
Strict

Import brl.linkedlist
Import brl.standardio
Import brl.pixmap

Include "geom.bmx"

Public

Type TB3DTexture
	Field	name$
	Field	flags
	Field	blend
	Field	posx#
	Field	posy#
	Field	sclx#
	Field	scly#
	Field	rot#
	
	Method Read:TB3DTexture(stream:TStream)
		name$=stream.ReadLine()
		flags=stream.ReadInt()
		blend=stream.ReadInt()
		posx=stream.ReadFloat()
		posy=stream.ReadFloat()
		sclx=stream.ReadFloat()
		scly=stream.ReadFloat()
		rot=stream.ReadFloat()
		Return Self
	End Method		
End Type

Type TB3DBrush
	Field name$
	Field col#[4]
	Field shi#,blend,fx
	Field tex[]
	
	Method Read:TB3DBrush(stream:TStream,n_texs)		
		name$=stream.ReadLine()
		For Local i=0 Until 4
			col[i]=stream.ReadFloat()
		Next
		shi=stream.ReadFloat()
		blend=stream.ReadInt()
		fx=stream.ReadInt()
		tex=New Int[n_texs]
		For Local i=0 Until n_texs
			tex[i]=stream.ReadInt()
		Next
		Return Self
	End Method
End Type

Type TB3DTriangle
	Field v0,v1,v2
	Field brushid
	
	Method Read:TB3DTriangle(stream:TStream,id)
		v0=stream.ReadInt()
		v1=stream.ReadInt()
		v2=stream.ReadInt()
		brushid=id
		Return Self
	End Method
End Type

Type TB3DVertex
 	Field pos:TVec3
 	Field norm:TVec3
 	Field tex:TVec3

	Method Set(p:TVec3,n:TVec3,t:TVec3)
		pos=p
		norm=n
		tex=t
	End Method
End Type


Type TB3DMesh
	Field brush
	Field flags,tc_sets,tc_size
	Field vertlist:TList=New TList
	Field trilist:TList=New TList
	
	Method ToString$()
		Return "verts:"+vertlist.count()+" tris="+trilist.count()
	End Method
End Type

Type TB3DBone
	Field vert
	Field weight#
End Type

Type TB3DKey
	Field flags,frame
	Field pos:TVec3
	Field scale:TVec3
	Field rot:TQuat		
End Type

Type TB3DAnim
	Field flags,frames,fps#
End Type

Type TB3DNode
	Field parent:TB3DNode		
	Field kids:TList
	Field name$
	Field pos:TVec3
	Field scale:TVec3
	Field rot:TQuat		
	Field mesh:TB3DMesh
	Field bones:TList
	Field keys:TList
	Field anims:TList	
	Method SetParent(p:TB3DNode)
		parent=p
		If p
			If Not p.kids p.kids=New TList
			p.kids.AddLast Self
		EndIf
	End Method			
End Type

Type TB3DStream Extends TStreamWrapper
	Field chunklist:TList=New TList
	Field brushlist:TList=New TList
	Field texturelist:TList=New TList
	Field root:TB3DNode
	
	Method ReadChunk$()
		Local	tag$,size
		tag$=ReadString(4)
		size=ReadInt()
		chunklist.AddLast String(Pos()+Size)
		Return tag$
	End Method
	
	Method ExitChunk()
		Local	pos
		pos=Int(String(chunklist.RemoveLast()))
		Seek pos
	End Method
	
	Method ChunkSize()
		Local p=Int(String(chunklist.Last()))
		Return p-Pos()
	End Method

	Method ReadVec3:TVec3()
		Local v:TVec3=New TVec3
		v.x=ReadFloat()
		v.y=ReadFloat()
		v.z=ReadFloat()
		Return v		
	End Method

	Method ReadQuat:TQuat()
		Local q:TQuat=New TQuat
		q.x=ReadFloat()
		q.y=ReadFloat()
		q.z=ReadFloat()
		q.w=ReadFloat()
		Return q		
	End Method

	Method ReadTextures()
		While ChunkSize()
			texturelist.addlast New TB3DTexture.Read(Self)
		Wend
	End Method

	Method ReadBrushes()
		Local n_texs=ReadInt()
		While ChunkSize()
			brushlist.addlast New TB3DBrush.Read(Self,n_texs)
		Wend
	End Method	
	
	Method ReadVertices(mesh:TB3DMesh)
		mesh.flags=ReadInt()
		mesh.tc_sets=ReadInt()
		mesh.tc_size=ReadInt()
		Local i
		
		Local tcoord:TVec3=New TVec3
		While ChunkSize()
			Local pos:TVec3
			Local norm:TVec3
			Local color:TVec3
			pos=ReadVec3()
			If mesh.flags&1 norm=ReadVec3()
			If mesh.flags&2 color=ReadVec3()
			For Local k=0 Until mesh.tc_sets
				If mesh.tc_size=2
					tcoord.x=ReadFloat()
					tcoord.y=ReadFloat()
				Else
					For Local j=0 Until mesh.tc_size
						DebugStop
						ReadFloat()
					Next
				EndIf
			Next			
			Local vertex:TB3DVertex=New TB3DVertex
			vertex.Set pos,norm,tcoord
			mesh.vertlist.AddLast vertex
		Wend
	End Method
	
	Method ReadTriangles(mesh:TB3DMesh)
		Local brush_id=ReadInt()
'		Brush b=brush_id>=0 ? brushes[brush_id] : Brush();
		While ChunkSize()
			mesh.trilist.AddLast New TB3DTriangle.Read(Self,brush_id)
		Wend
	End Method

	Method ReadMesh:TB3DMesh(node:TB3DNode)
		Local mesh:TB3DMesh=New TB3DMesh	
		mesh.brush=ReadInt()
		While ChunkSize()
			Select ReadChunk()
				Case "VRTS"
					ReadVertices(mesh)
				Case "TRIS"
					ReadTriangles(mesh)
			End Select
			ExitChunk()
		Wend
		node.mesh=mesh
	End Method
	
	Method ReadBones(node:TB3DNode)
		Local bonelist:TList=New TList
		Local bone:TB3DBone
		Local vert,weight#
		While ChunkSize()
			vert=ReadInt()
			weight=ReadFloat()
			bone=New TB3DBone
			bone.vert=vert
			bone.weight=weight
			bonelist.AddLast bone
		Wend
		node.bones=bonelist
	End Method				
	
	Method ReadKeys(node:TB3DNode)
		Local keylist:TList=New TList
		Local key:TB3DKey
		Local flags
		flags=ReadInt()		
		While ChunkSize()
			key=New TB3DKey
			key.frame=ReadInt()
			If flags&1 key.pos=ReadVec3()
			If flags&2 key.scale=ReadVec3()
			If flags&4 key.rot=ReadQuat()
			keylist.AddLast key
		Wend
		node.keys=keylist
	End Method				
	
	Method ReadAnims(node:TB3DNode)
		Local animlist:TList=New TList
		Local anim:TB3DAnim
		While ChunkSize()
			anim=New TB3dAnim
			anim.flags=ReadInt()
			anim.frames=ReadInt()
			anim.fps=ReadFloat()
			animlist.AddLast anim
		Wend
		node.anims=animlist
	End Method
	
	Method ReadNode:TB3DNode(parent:TB3DNode)
		Local node:TB3DNode
		node=New TB3DNode
		node.setparent parent
		node.name=ReadLine()
		node.pos=ReadVec3()
		node.scale=ReadVec3()
		node.rot=ReadQuat()
		While ChunkSize()
			Select ReadChunk()
				Case "MESH"	ReadMesh(node)
				Case "BONE" ReadBones(node)
				Case "KEYS" ReadKeys(node)
				Case "ANIM" ReadAnims(node)
				Case "NODE" ReadNode(node)
			End Select
			ExitChunk()
		Wend
		Return node
	End Method
	
	Method ReadB3DStream:TB3DStream(stream:TStream)
		SetStream stream
		Local tag$=ReadChunk()	
		If tag<>"BB3D" Close;Return
		Local version=ReadInt()	
		If version<>1 Close;Return
		While ChunkSize()
			Select ReadChunk()
				Case "TEXS"
					ReadTextures()
				Case "BRUS"
					ReadBrushes()
				Case "NODE"
					root=ReadNode(Null)
			End Select
			ExitChunk
		Wend		
		Close
		Return Self
	End Method
	
		
	Function Open:TB3DStream(file$)
		Local stream:TStream=ReadStream("littleendian::"+file)
		If stream Return New TB3DStream.ReadB3DStream(stream)		
	End Function
End Type

Type TB3DEntity
	Field brushlist:TList
	Field texturelist:TList
	Field root:TB3DNode
Rem
	Method BuildModel:TModel()
	 	Local model:TModel
		Local polys:TPolygon[]
		Local materials:Object[]
		Local m	
		materials=New Object[brushlist.count()]
		For Local b:TB3DBrush=EachIn brushlist
			Local t:TB3DTexture=TB3DTexture(texturelist.ValueAtIndex(b.tex[0]))
			materials[m]=LoadPixmap(t.name)
			m:+1
		Next	
		Local mesh:TB3DMesh
		mesh=root.mesh
		Local p
		polys=New TPolygon[mesh.trilist.count()]
		For Local t:TB3DTriangle=EachIn mesh.trilist
			Local v:TVertex[3]
			v[0]=TVertex(mesh.vertlist.ValueAtIndex(t.v0))
			v[1]=TVertex(mesh.vertlist.ValueAtIndex(t.v1))
			v[2]=TVertex(mesh.vertlist.ValueAtIndex(t.v2))
			polys[p]=TPolygon.Create(v,materials[t.brushid])	
			p:+1
		Next
		model=TModel.Create( polys )
		Return model
	End Method
EndRem
End Type

Function LoadB3D:TB3DEntity( file$,match$="" )
	Local b3d:TB3DStream
	Local entity:TB3DEntity
	
	b3d=TB3DStream.Open(file$)
	If Not b3d Return

	entity=New TB3DEntity
	entity.brushlist=b3d.brushlist
	entity.texturelist=b3d.texturelist
	entity.root=b3d.root
	
	Return entity
End Function

Rem		
Function LoadModelB3D( file$,match$="" )
	Local b3d:TB3DStream
	Local root:TB3DNode
	b3d=TB3DStream.Open(file$)
	If Not b3d Return
'	Return b3d.BuildModel()			
End Function 

	chunklist=New TList
	brushlist=New TList
	texturelist=New TList
	vertlist=New TList
	trilist=New TList

	Local tag$=ReadChunk()	
	If tag<>"BB3D" stream.Close;Return
	
	Local version=stream.ReadInt()	
	If version<>1 stream.Close;Return
	
	While ChunkSize()
		Select ReadChunk()
			Case "TEXS"
				ReadTextures()
			Case "BRUS"
				ReadBrushes()
			Case "NODE"
				Local obj:Object=ReadNode(Null)
		End Select
		ExitChunk
	Wend
			
	stream.Close
	
	Local model:TModel
	Local polys:TPolygon[]
	Local materials:Object[]

	Local m	
	materials=New Object[brushlist.count()]
	For Local b:TBrush=EachIn brushlist
		Local t:TTexture=TTexture(texturelist.ValueAtIndex(b.tex[0]))
		materials[m]=LoadPixmap(t.name)
'		If Not materials[m] DebugLog "Failed to load "+t.name
		m:+1
	Next
	
	Local p
	polys=New TPolygon[trilist.count()]
	For Local t:TTriangle=EachIn trilist
		Local v:TVertex[3]
		v[0]=TVertex(vertlist.ValueAtIndex(t.v0))
		v[1]=TVertex(vertlist.ValueAtIndex(t.v1))
		v[2]=TVertex(vertlist.ValueAtIndex(t.v2))
		polys[p]=TPolygon.Create(v,materials[t.brushid])	
		p:+1
	Next
	
	model=TModel.Create( polys )
	
'	model.SetMaterial pm

	Return model

EndRem

'Global stream:TStream
'Global texturelist:TList
'Global brushlist:TList
'Global vertlist:TList
'Global trilist:TList


Rem
						
												
																								
	Method ReadTextures()
		While ChunkSize()
			texturelist.addlast New TTexture.Read(stream)
		Wend
	End Method

	Method ReadBrushes()
		Local n_texs=stream.ReadInt()
		While ChunkSize()
			brushlist.addlast New TBrush.Read(stream,n_texs)
		Wend
	End Method
	
	Method ReadVertices()
		Local flags=stream.ReadInt()
		Local tc_sets=stream.ReadInt()
		Local tc_size=stream.ReadInt()
		Local i
		Local tcoord:TVec3=New TVec3
		While ChunkSize()
			Local pos:TVec3,norm:TVec3,color:TVec3
			pos=ReadVec3()
'			Print "ReadVertices:"+pos.x+","+pos.y+","+pos.z
			If flags&1 norm=ReadVec3()
			If flags&2 color=ReadVec3()
'			Print flags
'			Print "tc_sets="+tc_sets+" tc_size="+tc_size
			For Local k=0 Until tc_sets
				If tc_size=2
					tcoord.x=stream.ReadFloat()
					tcoord.y=stream.ReadFloat()
				Else
					For Local j=0 Until tc_size
						stream.readFloat()
					Next
				EndIf
			Next			
			vertlist.AddLast TVertex.Create( pos,norm,tcoord )
		Wend
		Return flags
	End Method
	
	Method ReadTriangles()
		Local brush_id=stream.ReadInt()
'		Brush b=brush_id>=0 ? brushes[brush_id] : Brush();
		While ChunkSize()
			Local v0,v1,v2
			v0=stream.ReadInt()
			v1=stream.ReadInt()
			v2=stream.ReadInt()
			trilist.AddLast TTriangle.Create(v0,v1,v2,brush_id)
'			Print "ReadTriangle:"+v0+","+v1+","+v2
		Wend
	End Method
	
				
	Method ReadBones()
		Local	vert,weight#
		vert=stream.ReadInt()
		weight=stream.ReadFloat()
'		Print "vert="+vert+" weight="+weight
	End Method				
	
	Method ReadKey()
		Local	flags,frame
		Local	pos:TVec3
		Local	scale:TVec3
		Local	rot:TQuat		
		flags=stream.ReadInt()
		frame=stream.ReadInt()
		If flags&1 pos=ReadVec3()
		If flags&2 scale=ReadVec3()
		If flags&4 rot=ReadQuat()
'		Print "key!"
	End Method				
	
	Method ReadAnim()
		Local	flags,frames,fps#
		flags=stream.ReadInt()
		While ChunkSize()
			frames=stream.ReadInt()
			fps=stream.ReadFloat()
			Print "frames="+frames+" fps="+fps
		Wend
	End Method
	
	Type TB3DNode
		Field parent:TB3DNode		
		Field name$
		Field pos:TVec3
		Field scale:TVec3
		Field rot:TQuat		
'		Field mesh:TB3DMesh
'		Field bone:TB3DBone
		Method ReadMesh()
			Local brush,flags					
			brush=stream.ReadInt()
			While ChunkSize()
				Select ReadChunk()
					Case "VRTS"
						flags=ReadVertices()
					Case "TRIS"
						ReadTriangles()
				End Select
				ExitChunk()
			Wend
		End Method
	End Type
	
	Method ReadNode:Object(parent:TB3DNode)
		Local	node:TB3DNode
		
		node=New TB3DNode
		node.parent=parent
		node.name=stream.ReadLine()
		node.pos=ReadVec3()
		node.scale=ReadVec3()
		node.rot=ReadQuat()
		Print "NODE:"
		While ChunkSize()
			Select ReadChunk()
				Case "MESH"	node.ReadMesh()
				Case "BONE" ReadBones 
				Case "KEYS" ReadKey
				Case "ANIM" ReadAnim
				Case "NODE" ReadNode Null
			End Select
			ExitChunk()
		Wend
		Return node
	End Method
				
End Method
EndRem

Rem
************************************************************************************
*                             Blitz3d file format V0.01                            *
************************************************************************************

This document and the information contained within is placed in the Public Domain.

Please visit http://www.blitzbasic.co.nz for the latest version of this document.

Please contact marksibly@blitzbasic.co.nz for more information and general inquiries.



************************************************************************************
*                                   Introduction                                   *
************************************************************************************

The Blitz3D file format specifies a format for storing texture, brush and entity descriptions for
use with the Blitz3D programming language.

The rationale behind the creation of this format is to allow for the generation of much richer and
more complex Blitz3D scenes than is possible using established file formats - many of which do not
support key features of Blitz3D, and all of which miss out on at least some features!

A Blitz3D (.b3d) file is split up into a sequence of 'chunks', each of which can contain data
and/or other chunks.

Each chunk is preceded by an eight byte header:

char tag[4]	;4 byte chunk 'tag'
int length	;4 byte chunk length (not including *this* header!)

If a chunk contains both data and other chunks, the data always appears first and is of a fixed
length.

A file parser should ignore unrecognized chunks.

Blitz3D files are stored little endian (intel) style.

Many aspects of the file format are not quite a 'perfect fit' for the way Blitz3D works. This has
been done mainly to keep the file format simple, and to make life easier for the authors of third
party importers/exporters.



************************************************************************************
*                                   Chunk Types                                    *
************************************************************************************

This lists the types of chunks that can appear in a b3d file, and the data they contain.

Color values are always in the range 0 to 1.

string (char[]) values are 'C' style null terminated strings.

Quaternions are used to specify general orientations. The first value is the quaternion 'w' value,
the next 3 are the quaternion 'vector'. A 'null' rotation should be specified as 1,0,0,0.

Anything that is referenced 'by index' always appears EARLIER in the file than anything that
references it.

brush_id references can be -1: no brush.

In the following descriptions, {} is used to signify 'repeating until end of chunk'. Also, a chunk
name enclosed in '[]' signifies the chunk is optional.

Here we go!


BB3D
  int version                 ;file format version: default=1
  [TEXS]                      ;optional textures chunk
  [BRUS]                      ;optional brushes chunk
  [NODE]                      ;optional node chunk

The BB3D chunk appears first in a b3d file, and its length contains the rest of the file.

Version is in major*100+minor format. To check the version, just divide by 100 and compare it with
the major version your software supports, eg:

if file_version/100>my_version/100
   RuntimeError "Can't handle this file version!"
EndIf

if file_version Mod 100>my_version Mod 100
   ;file is a more recent version, but should still be backwardly compatbile with what we can
handle!
EndIf


TEXS
  {
	char file[]                 ;texture file name
  int flags,blend             ;blitz3D TextureFLags and TextureBlend: default=1,2
	float x_pos,y_pos           ;x and y position of texture: default=0,0
	float x_scale,y_scale       ;x and y scale of texture: default=1,1
	float rotation              ;rotation of texture (in radians): default=0
  }

The TEXS chunk contains a list of all textures used in the file.

The flags field value can conditional an additional flag value of '65536'. This is used to indicate that the texture uses secondary UV values, ala the TextureCoords command. Yes, I forgot about this one.


BRUS
  int n_texs
  {
  char name[]                 ;eg "WATER" - just use texture name by default
  float red,green,blue,alpha  ;Blitz3D Brushcolor and Brushalpha: default=1,1,1,1
  float shininess             ;Blitz3D BrushShininess: default=0
  int blend,fx                ;Blitz3D Brushblend and BrushFX: default=1,0
  int texture_id[n_texs]      ;textures used in brush
  }

The BRUS chunk contains a list of all brushes used in the file.


VRTS:
  int flags                   ;1=normal values present, 2=rgba values present
  int tex_coord_sets          ;texture coords per vertex (eg: 1 for simple U/V) max=8
  int tex_coord_set_size      ;components per set (eg: 2 for simple U/V) max=4
  {
  float x,y,z                 ;always present
  float nx,ny,nz              ;vertex normal: present if (flags&1)
  float red,green,blue,alpha  ;vertex color: present if (flags&2)
  float tex_coords[tex_coord_sets][tex_coord_set_size]	;tex coords
  }

The VRTS chunk contains a list of vertices. The 'flags' value is used to indicate how much extra
data (normal/color) is stored with each vertex, and the tex_coord_sets and tex_coord_set_size
values describe texture coordinate information stored with each vertex.


TRIS:
  int brush_id                ;brush applied to these TRIs: default=-1
  {
  int vertex_id[3]            ;vertex indices
  }

The TRIS chunk contains a list of triangles that all share a common brush.


MESH:
  int brush_id                ;'master' brush: default=-1
  VRTS                        ;vertices
  TRIS[,TRIS...]              ;1 or more sets of triangles

The MESH chunk describes a mesh. A mesh only has one VRTS chunk, but potentially many TRIS chunks.


BONE:
  {
  int vertex_id               ;vertex affected by this bone
  float weight                ;how much the vertex is affected
  }

The BONE chunk describes a bone. Weights are applied to the mesh described in the enclosing ANIM -
in 99% of cases, this will simply be the MESH contained in the root NODE chunk.


KEYS:
  int flags		                ;1=position, 2=scale, 4=rotation
  {
  int frame                   ;where key occurs
  float position[3]           ;present if (flags&1)
  float scale[3]              ;present if (flags&2)
  float rotation[4]           ;present if (flags&4)
  }

The KEYS chunk is a list of animation keys. The 'flags' value describes what kind of animation
info is stored in the chunk - position, scale, rotation, or any combination of.


ANIM:
  int flags                   ;unused: default=0
  int frames                  ;how many frames in anim
  float fps                   ;default=60

The ANIM chunk describes an animation.


NODE:
  char name[]                 ;name of node
  float position[3]           ;local...
  float scale[3]              ;coord...
  float rotation[4]           ;system...
  [MESH|BONE]                 ;what 'kind' of node this is - if unrecognized, just use a Blitz3D
pivot.
  [KEYS[,KEYS...]]            ;optional animation keys
  [NODE[,NODE...]]            ;optional child nodes
  [ANIM]                      ;optional animation

The NODE chunk describes a Blitz3D Entity. The scene hierarchy is expressed by the nesting of NODE
chunks.

NODE kinds are currently mutually exclusive - ie: a node can be a MESH, or a BONE, but not both!
However, it can be neither...if no kind is specified, the node is just a 'null' node - in Blitz3D
speak, a pivot.

The presence of an ANIM chunk in a NODE indicates that an animation starts here in the hierarchy.
This allows animations of differing speeds/lengths to be potentially nested.

There are many more 'kind' chunks coming, including camera, light, sprite, plane etc. For now, the
use of a Pivot in cases where the node kind is unknown will allow for backward compatibility.



************************************************************************************
*                                    Examples                                      *
************************************************************************************

A typical b3d file will contain 1 TEXS chunk, 1 BRUS chunk and 1 NODE chunk, like this:

BB3D
  1
  TEXS
    ...list of textures...
  BRUS
    ...list of brushes...
  NODE
    ...stuff in the node...

A simple, non-animating, non-textured etc mesh might look like this:

BB3D
  1                           ;version
  NODE
    "root_node"               ;node name
    0,0,0                     ;position
    1,1,1                     ;scale
    1,0,0,0                   ;rotation
    MESH                      ;the mesh
      -1                      ;brush: no brush
      VRTS                    ;vertices in the mesh
        0                     ;no normal/color info in verts
        0,0                   ;no texture coords in verts
        {x,y,z...}            ;vertex coordinates
      TRIS                    ;triangles in the mesh
        -1                    ;no brush for this triangle
        {v0,v1,v2...}         ;vertices


A more complex 'skinned mesh' might look like this (only chunks shown):

BB3D
  TEXS                        ;texture list
  BRUS                        ;brush list
  NODE                        ;root node
    MESH                      ;mesh - the 'skin'
    ANIM                      ;anim
    NODE                      ;first child of root node -  eg: "pelvis"
      BONE                    ;vertex weights for pelvis
      KEYS                    ;anim keys for pelvis
      NODE                    ;first child of pelvis - eg: "left-thigh"
        BONE                  ;bone
        KEYS                  ;anim keys for left-thigh
      NODE                    ;second child of pelvis - eg: "right-thigh"
        BONE                  ;vertex weights for right-thigh
        KEYS                  ;anim keys for right-thigh

...and so on.
EndRem


Rem
KEYS:
  int flags		                ;1=position, 2=scale, 4=rotation
  {
  int frame                   ;where key occurs
  float position[3]           ;present if (flags&1)
  float scale[3]              ;present if (flags&2)
  float rotation[4]           ;present if (flags&4)
  }

The KEYS chunk is a list of animation keys. The 'flags' value describes what kind of animation
info is stored in the chunk - position, scale, rotation, or any combination of.


ANIM:
  int flags                   ;unused: default=0
  int frames                  ;how many frames in anim
  float fps                   ;default=60

The ANIM chunk describes an animation.


NODE:
  char name[]                 ;name of node
  float position[3]           ;local...
  float scale[3]              ;coord...
  float rotation[4]           ;system...
  [MESH|BONE]                 ;what 'kind' of node this is - if unrecognized, just use a Blitz3D pivot.
  [KEYS[,KEYS...]]            ;optional animation keys
  [NODE[,NODE...]]            ;optional child nodes
  [ANIM]                      ;optional animation


File opened...
Chunk:TEXS (size=82)
  .\fwnpc1.bmp
  .\grouge.bmp
Chunk:BRUS (size=92)
  1 - Default
  1 - Default
Chunk:NODE (size=31557)
  name=ROOT
  Chunk:MESH (size=13168)
    brush=-1
    Chunk:VRTS (size=7672)
      383 vertices
    Chunk:TRIS (size=5476)
      456 triangles
  Chunk:ANIM (size=12)
    0 106 0
  Chunk:NODE (size=18308)
    name=hips
    Chunk:BONE (size=192)
      24 weights
    Chunk:KEYS (size=516)
      32 keys
    Chunk:KEYS (size=644)
      32 keys
    Chunk:NODE (size=3173)
      name=r_hip
      Chunk:BONE (size=176)
        22 weights
      Chunk:KEYS (size=372)
        23 keys
      Chunk:KEYS (size=464)
        23 keys
      Chunk:NODE (size=2083)
        name=r_knee
        Chunk:BONE (size=104)
          13 weights
        Chunk:KEYS (size=388)
          24 keys
        Chunk:KEYS (size=484)
          24 keys
        Chunk:NODE (size=1028)
          name=r_ankle
          Chunk:BONE (size=120)
            15 weights
          Chunk:KEYS (size=372)
            23 keys
          Chunk:KEYS (size=464)
            23 keys
    Chunk:NODE (size=3173)
      name=l_hip
      Chunk:BONE (size=176)
        22 weights
      Chunk:KEYS (size=372)
        23 keys
      Chunk:KEYS (size=464)
        23 keys
      Chunk:NODE (size=2083)
        name=l_knee
        Chunk:BONE (size=104)
          13 weights
        Chunk:KEYS (size=388)
          24 keys
        Chunk:KEYS (size=484)
          24 keys
        Chunk:NODE (size=1028)
          name=l_ankle
          Chunk:BONE (size=120)
            15 weights
          Chunk:KEYS (size=372)
            23 keys
          Chunk:KEYS (size=464)
            23 keys
    Chunk:NODE (size=10517)
      name=spine1
      Chunk:BONE (size=80)
        10 weights
      Chunk:KEYS (size=436)
        27 keys
      Chunk:KEYS (size=544)
        27 keys
      Chunk:NODE (size=9378)
        name=spine2
        Chunk:BONE (size=368)
          46 weights
        Chunk:KEYS (size=356)
          22 keys
        Chunk:KEYS (size=444)
          22 keys
        Chunk:NODE (size=3347)
          name=r_shldr
          Chunk:BONE (size=184)
            23 weights
          Chunk:KEYS (size=404)
            25 keys
          Chunk:KEYS (size=504)
            25 keys
          Chunk:NODE (size=2175)
            name=r_elbow
            Chunk:BONE (size=224)
              28 weights
            Chunk:KEYS (size=388)
              24 keys
            Chunk:KEYS (size=484)
              24 keys
            Chunk:NODE (size=999)
              name=r_hand
              Chunk:BONE (size=200)
                25 weights
              Chunk:KEYS (size=324)
                20 keys
              Chunk:KEYS (size=404)
                20 keys
        Chunk:NODE (size=3311)
          name=l_shldr
          Chunk:BONE (size=184)
            23 weights
          Chunk:KEYS (size=404)
            25 keys
          Chunk:KEYS (size=504)
            25 keys
          Chunk:NODE (size=2139)
            name=l_elbow
            Chunk:BONE (size=224)
              28 weights
            Chunk:KEYS (size=372)
              23 keys
            Chunk:KEYS (size=464)
              23 keys
            Chunk:NODE (size=999)
              name=l_hand
              Chunk:BONE (size=200)
                25 weights
              Chunk:KEYS (size=324)
                20 keys
              Chunk:KEYS (size=404)
                20 keys
        Chunk:NODE (size=1457)
          name=neck
          Chunk:BONE (size=408)
            51 weights
          Chunk:KEYS (size=436)
            27 keys
          Chunk:KEYS (size=544)
            27 keys
EndRem

Rem	
	Method ReadObject:Object(parent:Object)
		Local name$=stream.ReadLine()
'		Print "ReadObject:"+name
		Local i,pos#[3],scl#[3],rot#[4]
		For i=0 To 2
			pos[i]=stream.ReadFloat()
		Next
		For i=0 To 2
			scl[i]=stream.ReadFloat()
		Next
		For i=0 To 3
			rot[i]=stream.ReadFloat()
		Next
		While ChunkSize()
			Select ReadChunk()
				Case "MESH"	ReadMesh
				Case "BONE" ReadBones 
				Case "KEYS" ReadKey
				Case "ANIM" ReadAnim
				Case "NODE" ReadNode
			End Select
			ExitChunk()
		Wend
	End Method
EndRem
